package com.immediatus.engine.modifier;


import com.immediatus.engine.contracts.IModifier;

public class SequenceModifier<T> extends BaseModifier<T> implements IModifier.IModifierListener<T>{

    private ISubSequenceModifierListener<T> _modifierListener;
    private final IModifier<T>[] _sequenceModifiers;
    private int _currentModifierIndex;
    private float _secondsElapsed;
    private final float _duration;
    private boolean _finishedCached;

    public SequenceModifier(final IModifier<T>... modifiers_) throws IllegalArgumentException{
        this(null, null, modifiers_);
    }

    public SequenceModifier(final ISubSequenceModifierListener<T> listener_, final IModifier<T>... modifiers_) throws IllegalArgumentException{
        this(listener_, null, modifiers_);
    }

    public SequenceModifier(final IModifierListener<T> listener_, final IModifier<T>... modifiers_) throws IllegalArgumentException{
        this(null, listener_, modifiers_);
    }

    public SequenceModifier(final ISubSequenceModifierListener<T> sequenceModifierListener_, final IModifierListener<T> modifierListener_, final IModifier<T>... modifiers_) throws IllegalArgumentException{
        super(modifierListener_);
        if (modifiers_.length == 0)
            throw new IllegalArgumentException("modifier must not be empty");

        this._modifierListener = sequenceModifierListener_;
        this._sequenceModifiers = modifiers_;

        this._duration = getSequenceDurationOfModifier(modifiers_);

        modifiers_[0].addModifierListener(this);
    }

    protected SequenceModifier(final SequenceModifier<T> modifier_){
        this._duration = modifier_._duration;

        final IModifier<T>[] otherModifiers = modifier_._sequenceModifiers;
        this._sequenceModifiers = new IModifier[otherModifiers.length];

        final IModifier<T>[] shapeModifiers = this._sequenceModifiers;
        for (int i = shapeModifiers.length - 1; i >= 0; i--)
            shapeModifiers[i] = otherModifiers[i].deepCopy();

        shapeModifiers[0].addModifierListener(this);
    }

    @Override
    public SequenceModifier<T> deepCopy(){
        return new SequenceModifier<T>(this);
    }

    public ISubSequenceModifierListener<T> getSubSequenceModifierListener(){
        return this._modifierListener;
    }

    public void setSubSequenceModifierListener(final ISubSequenceModifierListener<T> listener_){
        this._modifierListener = listener_;
    }

    @Override
    public float getSecondsElapsed(){
        return this._secondsElapsed;
    }

    @Override
    public float getDuration(){
        return this._duration;
    }

    @Override
    public float onUpdate(final float secondsElapsed_, final T item_){
        if (this.isFinished()){
            return 0;
        } else{
            float secondsElapsedRemaining = secondsElapsed_;
            this._finishedCached = false;
            while (secondsElapsedRemaining > 0 && !this._finishedCached){
                secondsElapsedRemaining -= this._sequenceModifiers[this._currentModifierIndex].onUpdate(secondsElapsedRemaining, item_);
            }
            this._finishedCached = false;

            final float secondsElapsedUsed = secondsElapsed_ - secondsElapsedRemaining;
            this._secondsElapsed += secondsElapsedUsed;
            return secondsElapsedUsed;
        }
    }

    @Override
    public void reset(){
        if (this.isFinished()){
            this._sequenceModifiers[this._sequenceModifiers.length - 1].removeModifierListener(this);
        } else{
            this._sequenceModifiers[this._currentModifierIndex].removeModifierListener(this);
        }

        this._currentModifierIndex = 0;
        this.setFinished(false);
        this._secondsElapsed = 0;

        this._sequenceModifiers[0].addModifierListener(this);

        final IModifier<T>[] shapeModifiers = this._sequenceModifiers;
        for (int i = shapeModifiers.length - 1; i >= 0; i--){
            shapeModifiers[i].reset();
        }
    }

    @Override
    public void onModifierStarted(final IModifier<T> modifier_, final T item_){
        if (this._currentModifierIndex == 0){
            this.onModifierStarted(item_);
        }

        if (this._modifierListener != null){
            this._modifierListener.onSubSequenceStarted(modifier_, item_, this._currentModifierIndex);
        }
    }

    @Override
    public void onModifierFinished(final IModifier<T> modifier_, final T item_){
        if (this._modifierListener != null){
            this._modifierListener.onSubSequenceFinished(modifier_, item_, this._currentModifierIndex);
        }
        modifier_.removeModifierListener(this);

        this._currentModifierIndex++;

        if (this._currentModifierIndex < this._sequenceModifiers.length){
            final IModifier<T> nextSubSequenceModifier = this._sequenceModifiers[this._currentModifierIndex];
            nextSubSequenceModifier.addModifierListener(this);
        } else{
            this.setFinished(true);
            this._finishedCached = true;

            this.onModifierFinished(item_);
        }
    }

    public interface ISubSequenceModifierListener<T>{
        public void onSubSequenceStarted(final IModifier<T> pModifier, final T pItem, final int pIndex);
        public void onSubSequenceFinished(final IModifier<T> pModifier, final T pItem, final int pIndex);
    }

    protected static float getSequenceDurationOfModifier(final IModifier<?>[] modifiers_){
        float duration = Float.MIN_VALUE;

        for(int i = modifiers_.length - 1; i >= 0; i--)
            duration += modifiers_[i].getDuration();

        return duration;
    }
}
